using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MessageServer.utilities;
using MessageServer.Handlers;
using MessageServer.config;
using MessageServer.persistence;

namespace MessageServer.channels
{
    /**
     * A Factory to construct the pipeline. 
     * */
    public interface PipeLineFactory
    {
        //attribute to access the pipe that is constructed by this factory.
        ChannelPipeline pipeline { get; }
    }

    /**
     * This is a pipe through which messages are streamed up and down for a given
     * channel. Register handlers into the pipe in the order they have to be called.
     * 
     * */
    public interface ChannelPipeline
    {
        /** Add as the first handler to be called when streamed up and first to be called
         *  when streamed down. Each handler can be StreamUpHandler, StreamDownHandler or
         *  both. Which ever is the first StreamUpHandler from this object is called first.
         *  Each of the handler registered is known by the handle passed, so this has to be
         *  unique across handlers registered.
         **/
        void addFirst(String handle, Handler handler);

        /** Add as the last handler to be called when streamed up and first to be called
          * when streamed down. Each handler can be StreamUpHandler, StreamDownHandler or
          *  both. Which ever is the first StreamUpHandler from this object is called first.
          * */
        void addLast(String handle, Handler handler);

        /**
         * Add the handler passed before the handle recognized by childhandle and add the
         * current handler with the handle.
         * */
        void addBefore(String childhandle, String handle, Handler handler);

        /**
         * Add the handler passed after the handle recognized by parenthandle and add the
         * current handler with the handle.
         * */
        void addAfter(String parenthandle, String handle, Handler handler);

        /**
         * Remove the handler recognized by the handle passed from the list
         * */
        void remove(String handle);

        /**
         * Stream the event passed up the list of handlers, this will stream from
         * first StreamUpHandler to the last StreamUp Handler
         * */
        void streamup(ChannelEvent evt);

        /**
         * Stream the event passed down the list of handlers, this will stream from
         * last StreamdownHandler to the first Streamdown Handler
         * */
        void streamdown(ChannelEvent evt);

        /**
         * Return the sink that this pipe will sink to when the event is streamed down
         * */
        Sink sink { get; }

        /**
         * attach the channel and sink to this
         * */
        void attach(Channel channel, Sink sink);
    }

    /**
     * A default implementation of the ChannelPipe. This uses the pipeline functionality
     * to achieve a lock free implementation of adding handlers and removing handlers
     * 
     * */
    public class DefaultChannelPipeline : ChannelPipeline
    {
        Channel _channel;       //The channel in which the event streaming is occurring.
        Sink _sink;             //The sink to which events streamed down will be sunk

        Pipeline<DefaultHandlerContext> _pipe; //The pipe that will hold the Context for handlers to stream to.

        //Default constructor. Does not have any handlers or sink.
        public DefaultChannelPipeline()
        {
            _pipe = new Pipeline<DefaultHandlerContext>();
        }

        /**
         * Attach this pipeline to the given channel and sink to sink the events from
         * the channel to. This will throw an exception if it is an already attached
         * pipeline.
         * */
        public void attach(Channel channel, Sink sink)
        {
            if ((channel == null) || (sink == null))
                throw new Exception("Cannot attach to null channel or sink");

            if ((_channel != null) && (_sink != null))
                throw new Exception("Already attached to a channel/sink");

            _channel = channel;
            _sink = sink;
        }

        /**
         * Detach the channel and sink to which this pipe was attached to.
         * */
        public void detach()
        {
            _channel = null;
            _sink = null;
        }

        //Remove the handler recognized by handle.
        public void remove(String handle)
        {
            _pipe.remove(handle);
        }

        //Attribute to access the sink for this pipe
        public Sink sink { get { return _sink; } }

        //refer to the ChannelPipeline comments
        public void addFirst(String handle, Handler handler)
        {
            DefaultHandlerContext ctx = new DefaultHandlerContext(_pipe, this, handler);
            _pipe.addFirst(handle, ctx);
        }

        //refer to the ChannelPipeline comments
        public void addLast(String handle, Handler handler)
        {
            DefaultHandlerContext ctx = new DefaultHandlerContext(_pipe, this, handler);
            _pipe.addLast(handle, ctx);
        }

        //refer to the ChannelPipeline comments
        public void addBefore(String childhandle, String handle, Handler handler)
        {
            DefaultHandlerContext ctx = new DefaultHandlerContext(_pipe, this, handler);
            _pipe.addBefore(childhandle, handle, ctx);
        }

        //refer to the ChannelPipeline comments
        public void addAfter(String parenthandle, String handle, Handler handler)
        {
            DefaultHandlerContext ctx = new DefaultHandlerContext(_pipe, this, handler);
            _pipe.addAfter(parenthandle, handle, ctx);
        }

        //refer to the ChannelPipeline comments
        public virtual void streamup(ChannelEvent evt)
        {
            streamup(_pipe.first, evt);
        }

        private delegate HandlerState executeStream(HandlerContext ctx, ChannelEvent evt);
        private delegate HandlerContext nextStream(HandlerContext ctx);

        private HandlerState trackEvent(HandlerContext ctx, ChannelEvent evt, 
            nextStream nxt, executeStream stream)
        {
            HandlerState res = HandlerState.gonext;
            try
            {
                switch (evt.tracker.result)
                {
                    case HandlerState.gonext:
                    case HandlerState.errorcontinue:
                        {
                            HandlerContext validctx = nxt(ctx);
                            if (validctx != null)
                            {
                                res = stream(validctx, evt);
                                if ((res == HandlerState.error) || (res == HandlerState.errorcontinue))
                                    evt.tracker.succeeded = false;
                            }
                            else
                            {
                                evt.tracker.succeeded = true;
                                res = HandlerState.done;
                            }
                        }
                        break;
                    case HandlerState.error:
                        {
                            evt.tracker.succeeded = false;
                            res = HandlerState.done;
                        }
                        break;
                    default:
                        evt.tracker.succeeded = true;
                        res = HandlerState.done;
                        break;
                }

                evt.tracker.result = res;
            }
            catch (Exception e)
            {
                //handle any exceptions in handler execution
                ChannelLogger.LOG.LogError(e, e.Message);
                ChannelUtils.fireExceptionEvent(evt, e);
            }

            return res;
        }

        private HandlerState _streamup(HandlerContext ctx, ChannelEvent evt)
        {
            return ((UpStreamHandler)(ctx.handler)).handleUpStream(ctx, evt);
        }


        /*
         * Internal function to be called to stream up the event evt. It starts
         * at ctx searches for the next streamupcontext and streams to it. The context
         * contains the context in which the handler should execute. The handler can
         * modify this context and it will be maintained into the next handler to be
         * called. This is a synchronous call. Once the event is done, the event tracker
         * is set to done.
         * */
        internal HandlerState streamup(HandlerContext ctx, ChannelEvent evt)
        {
            HandlerState res = trackEvent(ctx, evt, getNextUpStreamCtx, _streamup);
            if (res == HandlerState.done)
            {
                evt.tracker.done = true; //set that the task is done.
            }
            return res;
        }

        //helper function to determine the next handler to call.
        private HandlerContext getNextUpStreamCtx(HandlerContext ctx)
        {
            DefaultHandlerContext nxt = ctx as DefaultHandlerContext;
            while ((nxt != null) && !nxt.canStreamUp())
            {
                nxt = nxt.next;
            }

            return nxt;
        }

        //helper function to determine the next downstream handler to call.
        private HandlerContext getPreviousDownStreamCtx(HandlerContext ctx)
        {
            DefaultHandlerContext prev = ctx as DefaultHandlerContext;
            while ((prev != null) && !prev.canStreamDown())
            {
                prev = prev.previous;
            }

            return prev;
        }

        //refer to the ChannelPipeline comments
        public virtual void streamdown(ChannelEvent evt)
        {
            streamdown(_pipe.last, evt);
        }

        private HandlerState _streamdown(HandlerContext ctx, ChannelEvent evt)
        {
            return ((DownStreamHandler)(ctx.handler)).handleDownStream(ctx, evt);
        }

        /*
         * Internal function to be called to stream down the event evt. It starts
         * at ctx searches for the next streamdowncontext and streams to it. The context
         * contains the context in which the handler should execute. The handler can
         * modify this context and it will be maintained into the next handler to be
         * called. When all handlers are called, this function calls the sinkEvent 
         * in the sink attached to. This is a synchronous call. Once the sinkEvent
         * is completed, the event is marked as done.
         * */
        internal HandlerState streamdown(HandlerContext ctx, ChannelEvent e)
        {
            HandlerState res = trackEvent(ctx, e, getPreviousDownStreamCtx, _streamdown);
            try
            {
                if (res == HandlerState.done)
                {
                    _sink.sinkEvent(this, e);
                    e.tracker.done = true;
                }
            }
            catch (Exception except)
            {
                //handle any exceptions in sink execution
                ChannelLogger.LOG.LogError(except, except.Message);
                ChannelUtils.fireExceptionEvent(e, except);
            }

            return res;
        }
    }

    /**
     * The Default pipeline factory for the defaultpipeline class.
     * */
    class DefaultPipelineFactory : PipeLineFactory
    {
        Handler[] _handlers;
        MessageReader _reader;
        TuningParams _parms;
        MessageServerConfig _config;
        
        public DefaultPipelineFactory(MessageServerConfig conf, TuningParams parms)
        {
            _config = conf;
            _parms = parms;
            _reader = new SimpleReader();
        }
        
        public DefaultPipelineFactory(MessageReader read, MessageServerConfig config, TuningParams parms)
        {
            _config = config;
            _parms = parms;
            _handlers = null;
            _reader = read;
        }

        public DefaultPipelineFactory(Handler[] handlers, MessageReader reader)
        {
            _handlers = handlers;
            _reader = reader;
        }

        private ChannelPipeline createNew()
        {
            ChannelPipeline pipe = new DefaultChannelPipeline();
            //add the Reader as the first handler
            MessageFormatHandler read = new MessageFormatHandler();
            read.registerReader(_reader);
            pipe.addLast(read.handle, read); //add the reader as the first handler
            if (_config.persistentMessages)
            {
                MessagePersistenceHandler persist = new MessagePersistenceHandler(_config, _parms);
                pipe.addLast(persist.handle, persist);
            }

            //add the processor as the next handler
            MessageProcessorHandler process = new MessageProcessorHandler(_config, _parms);
            pipe.addLast(process.handle, process);
            
            if ((_handlers != null) && (_handlers.Length > 0))
            {
                for (int i = 0; i < _handlers.Length; i++)
                {
                    pipe.addLast(_handlers[0].handle, _handlers[i]);
                }
            }

            return pipe;
        }

        //return a new DefaultChannelPipeline class.
        public ChannelPipeline pipeline { get { return createNew();  } }
    }
}
